﻿#region Namespace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DCT.Huiteng.Utility;
using DCT.Huiteng.Utility.Config;
using DCT.Huiteng.Utility.Log;
using DCT.Huiteng.Utility.Miscellaneous;
using System.Collections.Specialized;
using System.Dynamic;
using System.Web.Caching; 
#endregion

namespace DCT.Huiteng.AdminSystem.Web.Codes
{
    public class SSOTokenValidator
    {
        #region Generate
        public static bool Generate(out string token, out bool runerror)
        {
            string logtag = "SSOTokenValidator.Generate";

            token = "";
            bool succ = false;
            bool throwerror = false;

            var qe = GeneralToolCode.CreateQueueExecutionInstance(logtag);
            qe.RunThrowErrorEvent += (_sender, _e) => {
                throwerror = true;
            };
            succ = qe.Run(new Func<QueueExecutionContext, bool>[] {                
                Generate_Initial,               //初始化数据
                Generate_CheckSource,           //检查source
                Generate_CheckIP,               //检查IP合法性
                Generate_CheckTick,             //检查时间
                Generate_CheckMD5Signature,     //检查MD5签名合法性
                Generate_CreateToken            //生成验证Token
            });
            dynamic contextData = qe.Context["data"];

            if (succ) {                
                token = contextData.Token;
            }

            runerror = throwerror;

            contextData.LogContents["succ"] = succ.ToString();
            contextData.LogContents["runerror"] = runerror.ToString();
            //记录日志
            LogData ld = new LogData()
            {
                Tag = logtag,
                Level = succ ? LogLevel.Info : LogLevel.Error,
                Msg = LogExtension.FormatDictory(contextData.LogContents)
            };
            Logger.Instance.Log(ld);

            return succ;
        }

        private static bool Generate_Initial(QueueExecutionContext qec)
        {
            HttpContext context = HttpExtUtility.CurrentHttpContext;
            string ip = HttpExtUtility.GetClientIP();
            var queryStrings = HttpExtUtility.CurrentHttpContext.Request.QueryString;

            //日志内容
            Dictionary<string, string> logContents = new Dictionary<string, string>();
            logContents["url"] = context.Request.Url.AbsoluteUri;
            logContents["ip"] = ip;

            //初始化上下文数据 
            dynamic contextData = new ExpandoObject();
            contextData.HttpContext = context;
            contextData.QueryStrings = queryStrings;
            contextData.LogContents = logContents;
            contextData.IP = ip;

            //qec["data"] = data;
            qec["data"] = contextData;

            return true;    //初始化数据，肯定执行成功
        }

        private static bool Generate_CheckSource(QueueExecutionContext qec)
        {
            dynamic contextData = qec["data"];

            bool succ = true;

            //检验source，来源
            string source = contextData.QueryStrings["source"];    
            var kv = GeneralToolCode.LoadConfig("sso").GetKV(source);
            if (kv == null)
            {
                succ = false;
                contextData.LogContents["error"] = "source_invalid";
                contextData.LogContents["source"] = source;
            }
            else
            {
                //获取配置数据
                contextData.Config = CollectionUtility.Convert(kv);
            }
            return succ;
        }

        private static bool Generate_CheckIP(QueueExecutionContext qec)
        {
            dynamic contextData = qec["data"];

            bool succ = true;

            //检测IP
            if (!contextData.HttpContext.Request.IsLocal) //本地可以免IP认证
            {
                succ = contextData.Config["validIps"]
                    .Split(',')
                    .Contains(contextData.IP);
            }

            if (!succ)
            {
                //记录日志
                contextData.LogContents["error"] = "ip_invalid";
            }
            return succ;
        }

        private static bool Generate_CheckTick(QueueExecutionContext qec)
        {
            dynamic contextData = qec["data"];
            bool succ = false;

            string tickVal = contextData.QueryStrings["tick"];

            long tick = 0;
            if (!string.IsNullOrEmpty(tickVal) && long.TryParse(tickVal, out tick))
            {

                long now = DateTimeUtility.DateTimeFrom19700101(DateTime.Now);
                TimeSpan tickValidScope = TimeSpan.FromSeconds(Convert.ToDouble(contextData.Config["tickValidScope"]));  
                long validScope = tick + (long)tickValidScope.TotalMilliseconds;

                succ = validScope > now;

                if (!succ)
                {
                    //记录日志
                    contextData.LogContents["error"] = "tick_expired";
                    contextData.LogContents["urltick"] = tickVal;
                    contextData.LogContents["validScope"] = validScope.ToString();
                    contextData.LogContents["now"] = now.ToString();
                }
            }
            else {
                //记录日志
                contextData.LogContents["error"] = "tick_invalid";
                contextData.LogContents["urltick"] = tickVal;
            }

            return succ;
        }

        /// <summary>
        /// 校验Md5Key
        /// </summary>
        /// <param name="qec"></param>
        /// <returns></returns>
        private static bool Generate_CheckMD5Signature(QueueExecutionContext qec)
        {
            dynamic contextData = qec["data"];
            Dictionary<string, string> queryData = new Dictionary<string, string>();

            bool succ = true;

            NameValueCollection queryStrings = contextData.QueryStrings;
            string urlMD5Key = queryStrings["key"]; //Url传递的MD5

            Dictionary<string, string> config = contextData.Config;
            List<string> prepareRawString = new List<string>();
            //根据配置获取Url参数
            foreach (var query in config["queries"].Split(','))
            {
                string val = queryStrings[query] ?? "";
                queryData.Add(query, val);
                prepareRawString.Add(val);
            }
            //加入密钥
            prepareRawString.Add(config["secertKey"]);

            //合并字符串
            string prepareRaw = string.Join("", prepareRawString.ToArray());
            //md5加密
            string prepareMD5 = StringUtility.MD5(prepareRaw);
            //比对md5签名
            succ = string.Compare(urlMD5Key, prepareMD5, true) == 0;

            if (!succ)
            {
                //记录日志
                contextData.LogContents["error"] = "md5_invalid";
                contextData.LogContents["raw"] = prepareRaw;
                contextData.LogContents["md5"] = prepareMD5;
                contextData.LogContents["urlmd5"] = urlMD5Key;
            }
            else
            {
                //设置Url获取到的数据
                contextData.QueryData = queryData;
            }

            return succ;
        }

        private static bool Generate_CreateToken(QueueExecutionContext qec)
        {
            dynamic contextData = qec["data"];
            //把用户数据存在缓存
            HttpContext hc = contextData.HttpContext;

            //生成随机字符串
            string token = StringUtility.CreateRnd(10, false, true, false, false, "");  
            //追加时间Token，避免随机串重复
            token += DateTime.Now.Ticks.ToString();                                     

            AddCache(hc, token, contextData.QueryData);

            contextData.Token = token;

            //记录日志
            contextData.LogContents["token"] = token;

            return true;
        } 
        #endregion

        #region Check
        public static bool Check(string token, out Dictionary<string, string> cachedata)
        {
            bool valid = false;
            cachedata = GetCache(HttpExtUtility.CurrentHttpContext, token) as Dictionary<string, string>;
            valid = cachedata != null;
            return valid;
        } 
        #endregion

        #region 操作Cache
        private static readonly string cachePrefix = "ssotoken_";
        private static void AddCache(HttpContext hc, string token, object val)
        {
            hc.Cache.Add(cachePrefix + token
               , val
               , null
               , DateTime.Now.AddMinutes(5)    //有效期5分钟
               , Cache.NoSlidingExpiration
               , CacheItemPriority.Default
               , null);
        }

        private static object GetCache(HttpContext hc, string token)
        {
            string key = cachePrefix + token;            
            object val = hc.Cache.Remove(key);  //一次有效
            return val;
        } 
        #endregion
    }
}